package sbc.group1.lab1.mozartspaces;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.mozartspaces.capi3.CountNotMetException;
import org.mozartspaces.capi3.Selector;
import org.mozartspaces.core.ContainerReference;
import org.mozartspaces.core.Entry;
import org.mozartspaces.core.MzsConstants;
import org.mozartspaces.core.MzsCoreException;
import org.mozartspaces.core.MzsCoreRuntimeException;
import org.mozartspaces.core.RequestFuture;
import org.mozartspaces.core.TransactionReference;
import org.mozartspaces.core.MzsConstants.RequestTimeout;
import org.mozartspaces.core.util.Nothing;
import org.mozartspaces.notifications.Notification;
import org.mozartspaces.notifications.NotificationListener;
import org.mozartspaces.notifications.NotificationManager;
import org.mozartspaces.notifications.Operation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sbc.group1.lab1.common.Car;
import sbc.group1.lab1.common.Coatable;
import sbc.group1.lab1.common.Coater;
import sbc.group1.lab1.common.Order;
import sun.misc.Signal;
import sun.misc.SignalHandler;

class CoaterImpl implements Runnable, NotificationListener, Coater {
    public static final Logger log = LoggerFactory.getLogger(CoaterImpl.class);
    
    public static void main(String[] args) throws MzsCoreException, URISyntaxException, IOException, InterruptedException {
        CoaterImpl coater= new CoaterImpl(
                new File(args[0]).toURI().toURL(), 
                new URI(args[1]), 
                Integer.parseInt(args[2]),
                new Color(Integer.parseInt(args[3])));
        final Thread t = new Thread(coater, "coater");
        Signal.handle(new Signal("INT"), 
            new SignalHandler() {
                @Override
                public void handle(Signal sig) {
                    t.interrupt();
                }
            });
        
        if (System.getProperty("benchmark") != null && System.getProperty("benchmark").equals("true")) {
            Signal.handle(new Signal("HUP"), 
                    new SignalHandler() {
                        @Override
                        public void handle(Signal sig) {
                            t.start();
                        }
                    });
        }else {
            t.start();
            System.in.read();
            t.interrupt();
            t.join();
        }
    }

    private final SpaceClient space;
    private final List<Selector> uncoatedBodySelectors = new LinkedList<Selector>();
    private final List<Selector> uncoatedCarSelectors = new LinkedList<Selector>();
    private final int coaterId;
    
    private volatile boolean running;
    private volatile Notification notificationCar;
    private volatile boolean isWaitingForNotification = true;
    private Color coatingColor;
    private NotificationManager notificationManager;
    private Notification notificationCarBody;
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    private volatile Order currentOrder = null;
    private volatile boolean resetOrder=true;
    private Notification notificationOrder;
    
    public CoaterImpl(URL configFile, URI spaceURI, int coaterId, Color coatingColor) throws MzsCoreException {
        space= new SpaceClient();
        space.init(configFile, spaceURI);
        this.coaterId=coaterId;
        this.coatingColor=coatingColor;
        notificationManager = new NotificationManager(space.getCore());
        uncoatedBodySelectors.add(LabelCoatablesContainerAspect.CoatableLabel.NOT_COATED.getSelector());
        uncoatedCarSelectors.add(CarCoordinator.newSelector());
    }
    
    
    @Override
    public int getId() {
        return coaterId;
    }


    @Override
    public void run() {
        log.info("Starting");
        running=true;
        try {
            createNotifications();
            while(running) {
                try {
                    doWork();
                    log.debug("Coating done.");
                } catch (MzsCoreException e1) {
                    log.error("Coating car failed!",e1);
                } catch (InterruptedException e) {
                    log.info("Interrupted! Stopping...");
                    running=false;
                    return;
                }catch (MzsCoreRuntimeException e) {
                    if(e.getCause() instanceof InterruptedException) {
                        log.info("Interrupted! Stopping...");
                    }else {
                        log.error("", e);
                    }
                    running=false;
                    return;
                }
            }
        } catch (MzsCoreException e) {
            log.error("",e);
            return;
        } catch (InterruptedException e) {
           log.error("",e);
           return;
        }finally {
            try {
                destroyNotifications();
            } catch (Exception e) {
            }
            executor.shutdownNow();
            space.getCore().shutdown(true);
        }
    }
    
    private void doWork() throws MzsCoreException, InterruptedException {
        	synchronized (this) {
	            if (!coatCarsFirst()) {
	                waitForNotification();
	            }
        	}
    }
    
    private void waitForNotification() throws InterruptedException {
        isWaitingForNotification=true;
        while(isWaitingForNotification()) {
        	wait();
        }
        
    }


    private boolean coatCarsFirst() throws MzsCoreException, InterruptedException {
        TransactionReference transaction = space.getCapi().createTransaction(
                RequestTimeout.INFINITE, space.getSpaceUri());
        try {
        if (coatCar(transaction)) {
            transaction=null;
            return true;
        }else {
            space.getCapi().rollbackTransaction(transaction);
            transaction=null;
            transaction = space.getCapi().createTransaction(
                    RequestTimeout.INFINITE, space.getSpaceUri());
            if(coatCarBody(transaction)) {
                transaction=null;
                return true;
            }else {
                return false;
            }
        }
        }finally {
            if (transaction!=null) {
                try {
                    space.getCapi().rollbackTransaction(transaction);
                }catch(Exception e) {
                    log.error("",e);
                }
           }
        }
    }
    private boolean coatCar(final TransactionReference transaction) throws MzsCoreException, InterruptedException {
        try {
            return coat(transaction,space.getAssembledCarContainer(), 
            		uncoatedCarSelectors, 
                    space.getCompletedCarContainer(),null);
        }catch (CountNotMetException e) {
        	return false;
        }
    }
    
    private boolean coatCarBody(final TransactionReference transaction) throws MzsCoreException, InterruptedException {
        CoatingJob job = null;
        while(true) {
        	if (resetOrder) {
                resetOrder=false;
                currentOrder = getNextOrder(null);
            }
        	
        	if (currentOrder==null) {
        		return coatWithoutOrder(transaction);
        	}
        	try {
        	job= space.getCapi().<CoatingJob>take(space.getCoatingJobContainer(currentOrder),
                     (Selector)null,
                     MzsConstants.RequestTimeout.TRY_ONCE,
                     transaction).get(0);
        	}catch(CountNotMetException e) {
        	}
        	if (job == null) {
        		currentOrder=getNextOrder(currentOrder);
        	}else {
        		return (coat(transaction,space.getCarBodyContainer(), uncoatedBodySelectors, 
                        space.getCoatedCarBodyContainer(job.getOrder()),job.getOrder().getColor()));
        	}
        }
    }
    
    private boolean coatWithoutOrder(final TransactionReference transaction) throws MzsCoreException, InterruptedException {
        return coat(transaction,space.getCarBodyContainer(), uncoatedBodySelectors, 
                space.getCarBodyContainer(),getCoatingColor());
    }
    
    private boolean coat(final TransactionReference transaction, ContainerReference take, List<Selector> selectors, 
    		ContainerReference write, Color color) 
        throws MzsCoreException, InterruptedException {
    	
        Coatable coatable;
        try {
            coatable = space.getCapi().<Coatable>take(take, 
                    selectors,
                    RequestTimeout.TRY_ONCE, transaction).get(0);
            if (color == null) {
                if (coatable instanceof Car) {
                    Order order = ((Car)coatable).getOrder();
                    if (order==null) {
                        coatable.coat(getCoatingColor(), this);
                    }else {
                        coatable.coat(order.getColor(), this);
                    }
                }else {
                    coatable.coat(getCoatingColor(), this);
                }
            }else {
                coatable.coat(color, this);
            }
            RequestFuture<Nothing> writeOp;
            if (write == space.getCarBodyContainer()) {
                writeOp = 
                    space.getAcapi().write(write, RequestTimeout.INFINITE, 
                        transaction, new Entry(coatable, 
                                ColorCoordinator.newCoordinationData(
                                        coatable.getColor())));
            }else {
                writeOp = 
                    space.getAcapi().write(write, RequestTimeout.INFINITE, 
                        transaction, new Entry(coatable));
            }
            executor.execute(new FailedTransactionHandler( 
                    writeOp,
                    space.getAcapi().commitTransaction(transaction),
                    transaction, space.getCapi()));
            return true;
        }catch(CountNotMetException e) {
            return false;
        }
    }


    @Override
    public void entryOperationFinished(
            Notification source,
            Operation operation, List<? extends Serializable> entries) {
        if(source == notificationOrder) {
            for (Serializable entry : entries) {
                if (entry instanceof Entry) {
                    Serializable value =((Entry) entry).getValue();
                    if (value instanceof Order) {
                        Order order = (Order) value;
                        try {
                            notificationManager.createNotification(space.getCoatingJobContainer(order), this, Operation.WRITE);
                        } catch (MzsCoreException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }
            synchronized (this) {
                resetOrder=true;
                isWaitingForNotification=false;
                notifyAll();
            }
        }else {
            synchronized (this) {
                resetOrder=true;
                isWaitingForNotification=false;
                notifyAll();
            }
        }
    }

    private void destroyNotifications() throws MzsCoreException {
        notificationCar.destroy();
        notificationCarBody.destroy();
    }


    private void createNotifications() throws MzsCoreException, InterruptedException {
        HashSet<Operation> writeOp = new HashSet<Operation>();
        writeOp.add(Operation.WRITE);
        notificationCar=notificationManager.createNotification(
                space.getAssembledCarContainer(), 
                this, writeOp, (TransactionReference)null, null);
        notificationCarBody=notificationManager.createNotification(
                space.getCarBodyContainer(), 
                this, writeOp, (TransactionReference)null, null);
        notificationOrder = notificationManager.createNotification(
                space.getOrderContainer(), 
                this, writeOp, (TransactionReference)null, null);
        notificationManager.createNotification(
                space.getOrderJobsReissueContainer(), 
                this, writeOp, (TransactionReference)null, null);
    }
    
    private Color getCoatingColor() {
        return coatingColor;
    }


    private boolean isWaitingForNotification() {
        return isWaitingForNotification;
    }
    
    private Order getNextOrder(Order currentOrder) throws MzsCoreException {
        try {
            if (currentOrder==null) {
                return space.getCapi().<Order>read(space.getOrderContainer(),
                        OrderCoordinator.newSelector(1),
                        MzsConstants.RequestTimeout.TRY_ONCE, (TransactionReference)null).get(0);
            }else {
                return space.getCapi().<Order>read(space.getOrderContainer(),
                    OrderCoordinator.newSelector(1, currentOrder.getId()+1),
                    MzsConstants.RequestTimeout.TRY_ONCE, (TransactionReference)null).get(0);
            }
        }catch(CountNotMetException e) {
            return null;
        }
    }
}
